package com.example.exception;

import com.example.swagger.GlobalResultEnum;
import com.example.tools.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.xml.bind.ValidationException;
import java.nio.file.AccessDeniedException;
import java.text.DecimalFormat;
import java.util.Set;

/**
 * @author madman
 * @version 1.0
 * @date 2019/7/30 16:13
 * 全局的异常处理
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @Value("${spring.servlet.multipart.max-file-size:2MB}")
    private String maxFileSize;
    @Value("${spring.servlet.multipart.max-request-size:2MB}")
    private String maxRequestSize;

    /**
     * 请求参数错误
     */
    private final static String BASE_PARAM_ERR_MSG = "参数校验不通过";

    /**
     * 顶级的异常处理
     * 处理所有不可知异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public ApiResult handleException(Exception e) {
        // 打印异常堆栈信息
        log.error("顶级异常->:" + e.getMessage(), e);
        return ApiResult.failed(e.getMessage());
    }



    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({MultipartException.class})
    public ApiResult fileUpLoad2(MaxUploadSizeExceededException e) {
        log.error("上传文件异常 => : {}", e.getMessage());
        if (e.getCause().getCause() instanceof FileSizeLimitExceededException) {
            return ApiResult.failed("文件大小超出限制,允许的大小在" + maxFileSize);
        } else if (e.getCause().getCause() instanceof SizeLimitExceededException) {
            return ApiResult.failed("文件单次上传总大小超过限制,允许的大小在"+maxRequestSize);
        } else {
            return ApiResult.failed("文件上传失败");
        }
    }

    /**
     * 文件大小转换
     *
     * @param size
     * @return
     */
    private static String asReadableFileSize(long size) {
        if (size <= 0) {
            return "0";
        }
        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + "" + units[digitGroups];
    }


    /**
     * 自定义的异常处理
     * 业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    @ResponseStatus(HttpStatus.OK)
    public ApiResult handleBusinessException(ServiceException e) {
        // 打印异常堆栈信息
        log.error(" ---> 业务处理异常: {}", e.getMessage());
        return ApiResult.failed(e.getCode(), e.getMessage(), e.getData());
    }

    /**
     * 请求参数类型不匹配异常
     * 例如本应传递int，却传递了String
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public ApiResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.warn("方法参数类型不匹配异常: {}", e);
        return ApiResult.failed("方法请求参数类型不匹配异常");
    }

    /**
     * 缺少servlet请求参数抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({MissingServletRequestParameterException.class})
    public ApiResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.warn(" 参数错误: {}", e.getParameterName());
        return ApiResult.failed(BASE_PARAM_ERR_MSG + ":请求参数");
    }

    /**
     * 请求参数不能正确读取解析时，抛出的异常，比如传入和接受的参数类型不一致
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public ApiResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("参数解析失败：", e);
        return ApiResult.failed("传入和接受的参数类型不一致");
    }

    /**
     * 请求参数无效抛出的异常
     * 校验参数抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        String message = getBindResultMessage(result);
        log.error("参数验证失败：" + message);
        return ApiResult.failed(message.split(":")[1]);
    }

    /**
     * 构造参数校验错误信息
     *
     * @param result
     * @return
     */
    private String getBindResultMessage(BindingResult result) {
        // 多个错误随机取值的
        FieldError error = result.getFieldError();
        String field = error != null ? error.getField() : "空";
        String code = error != null ? error.getDefaultMessage() : "空";
        return String.format("%s:%s", field, code);
    }


    /**
     * 请求参数绑定到controller请求参数时的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    public ApiResult handleHttpMessageNotReadableException(BindException e) {
        BindingResult result = e.getBindingResult();
        String message = getBindResultMessage(result);
        log.error("参数绑定失败：{}", message);
        return ApiResult.failed("请求参数绑定失败");
    }

    /**
     * javax.validation:validation-api 校验参数抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({ConstraintViolationException.class})
    public ApiResult handleServiceException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String message = violation.getMessage();
        log.warn("参数验证失败：{}", message);
        return ApiResult.failed("参数验证失败:" + message);
    }

    /**
     * javax.validation 下校验参数时抛出的异常
     * 400 - Bad Request
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({ValidationException.class})
    public ApiResult<String> handleValidationException(ValidationException e) {
        log.warn("参数验证失败：{}", e);
        return ApiResult.failed("参数验证失败：" + e.getMessage());
    }

    /**
     * 不支持该请求方法时抛出的异常
     * 例如本应GET，结果却是POST
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public ApiResult<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        log.warn("不支持当前请求方法: {}", e);
        return ApiResult.failed("Request Method 不正确");
    }


    /**
     * 不支持当前媒体类型抛出的异常
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public ApiResult<String> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        log.warn("不支持当前媒体类型:{} ", e);
        return ApiResult.failed("不支持当前媒体类型");
    }

    /**
     * 操作数据库出现异常:名称重复，外键关联
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ApiResult<String> handleException(DataIntegrityViolationException e) {
        log.error("操作数据库出现异常:{}", e);
        return ApiResult.failed("操作数据库出现异常：字段重复、有外键关联等");
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(NoHandlerFoundException.class)
    public ApiResult<String> handlerNoFoundException(Exception e) {
        log.error("请求路径匹配异常:" + e.getMessage(), e);
        return ApiResult.failed(GlobalResultEnum.NOT_FOUND.getCode(), "路径不存在，请检查路径是否正确");
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(AccessDeniedException.class)
    public ApiResult<String> handleAuthorizationException(AccessDeniedException e) {
        log.warn(e.getMessage());
        return ApiResult.failed(GlobalResultEnum.FORBIDDEN.getCode(), "没有权限，请联系管理员授权");
    }
}

